﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;

namespace SecurityApp.Classes
{
    class Crypto
    {
        static String key1, key2, key3;

        #region ### Start Encryptie ###

        public static String tripleDES_Encrypt(String binaireInvoer, Boolean isDecryptie)
        {
            String first, second, third;

            // Encryptie of decryptie
            if (!isDecryptie)
            {
                first = startEncryptie(binaireInvoer, key1, false);
                second = startEncryptie(first, key2, false);
                third = startEncryptie(second, key3, false);
            }
            else
            {
                first = startEncryptie(binaireInvoer, key3, true);
                second = startEncryptie(first, key2, true);
                third = startEncryptie(second, key1, true);
            }

            if (third.Contains("abort"))
            {
                return "abort";
            }

            return third;
        }

        public static String startEncryptie(String binaireInvoer, String sleutel, Boolean isDecryptie)
        {
            try
            {
                StringBuilder binaireUitvoer = new StringBuilder();

                // Subkeys berekenen adhv de sleutel
                String[] sKeys = berekenSubKeys(sleutel);

                // Lengte van de invoer afronden op een veelvoud van 64 door voorloop nullen bij te voegen
                int aantalTekens = binaireInvoer.Length;
                int afronden = (int)Math.Ceiling(aantalTekens / 64.0);
                binaireInvoer = binaireInvoer.PadLeft(afronden * 64, '0');

                String[] tussenUitkomst = new String[(binaireInvoer.Length / 64)];

                // Parallel For loop om elke core te benutten (willekeurig wordt een loop met een bepaalde i waarden uitgevoerd)
                Parallel.For(0, (binaireInvoer.Length / 64), i =>
                {
                    String blok = "";

                    // Blok van 64bits selecteren
                    blok = binaireInvoer.Substring((int)i * 64, 64);

                    // Permuteren
                    String gepermuteerd = permuteerBlok(blok, DES.IP);

                    // L0 en R0 invullen
                    String L0 = gepermuteerd.Substring(0, 32);
                    String R0 = gepermuteerd.Substring(32, 32);

                    // 16 encryptie rondes doorlopen
                    String encryptie = encrypteerBlok(L0, R0, sKeys, isDecryptie);

                    // Permuteren
                    String versleuteldBlok = permuteerBlok(encryptie, DES.IP_INV);

                    tussenUitkomst[i] = versleuteldBlok;
                    //binaireUitvoer.Append(versleuteldBlok);
                });

                // Rangschikken van de versleutelde gegevens
                for (int i = 0; i < binaireInvoer.Length / 64; i++)
                {
                    binaireUitvoer.Append(tussenUitkomst[i]);
                }

                return binaireUitvoer.ToString();

            }
            catch (OutOfMemoryException e)
            {         
                // Komt enkel voor bij grote bestanden en te weinig vrij geheugen
                Console.WriteLine(e.Message);
                return "abort";
            }
            
        }

        private static String converteerNaarHex(String input)
        {
            // String wordt omgezet naar een array van chars
            char[] chars = input.ToCharArray();
            String output = "";

            // ELke char wordt omgezet naar zijn hexadecimale waarde
            foreach (char c in chars)
            {
                int waarde = Convert.ToInt32(c);
                output += String.Format("{0:X}", waarde);
            }

            return output;
        }

        public static ArrayList uniekeWaardes(String sleutel)
        {
            ArrayList waardes = new ArrayList();

            // Elk uniek karakter in de sleutel wordt toegevoegd
            foreach (char c in sleutel)
            {
                if (!(waardes.Contains(c)))
                {
                    waardes.Add(c);
                }  
            }

            return waardes;
        }

        public static Boolean isWeakKey(String key)
        {
            String hex1, hex2, hex3;

            // De 3 sleutels afleiden van de hoofdsleutel
            key1 = key.Substring(0, 8);
            key2 = key.Substring(8, 8);
            key3 = key.Substring(16);

            // Omzetten naar hexadecimaal om zo weak keys te kunnen checken
            hex1 = converteerNaarHex(key1);
            hex2 = converteerNaarHex(key2);
            hex3 = converteerNaarHex(key3);
            Boolean blnWeakKey = false;

            // Sleutels mogen geen 'weak keys' zijn
            Parallel.For(0, DES.weakKeys.Length, (i, loopState) =>
            {
                if (hex1 == DES.weakKeys[i].ToUpper() || hex2 == DES.weakKeys[i].ToUpper() || hex3 == DES.weakKeys[i].ToUpper())
                {
                    blnWeakKey = true;
                    loopState.Stop();
                }
            });

            // Als er een weak key gedetecteerd is wordt er null teruggegeven
            if (blnWeakKey)
            {
                return true;
            }
            else
            {
                // Bijkomend wordt er nagegaan of er genoeg verschillende karakters ingegeven zijn
                int aantalUniekeKarakters = uniekeWaardes(key).Count;

                if (aantalUniekeKarakters <= 5)
                {
                    return true;
                }
            }

            return false;
        }

        #region --- Encryptie ---

        private static String encrypteerBlok(String L0, String R0, String[] sKeys, Boolean isDecryptie)
        {
            String Ln, Rn, Rn_vorig, f;
            Rn = R0;
            Ln = L0;

            // 16 loops van het DES principe
            for (int i = 0; i < 16; i++)
            {
                // Voor Rn veranderd wordt moet de waarden bewaart worden
                Rn_vorig = Rn;

                // Als het gaat om een decryptie wordt de sleutel volgorde omgedraait
                if (isDecryptie)
                {
                    // F functie
                    f = F(Rn, sKeys[15 - i], i);
                }
                else
                {
                    // F functie
                    f = F(Rn, sKeys[i], i);
                }

                // XORen van de linkerhelft met de uitkomst van de F functie
                Rn = XOR(Ln, f);

                // De nieuwe linkerhelft wordt de vorige rechterhelft
                Ln = Rn_vorig;
            }

            // Wisselen van de helften
            String RL = Rn + Ln;
            return RL;
        }

        #endregion

        #endregion

        #region ### Permutatie ###

        // Het blok van 64bits ordenen volgens P
        private static String permuteerBlok(String blok, int[] reeks)
        {
            // StringBuilder met dezelfde lengte als het blok
            StringBuilder uitvoer = new StringBuilder(blok.Length);

            // In DES.P halen we welke bit in de blok-string we eerst volgend moeten plaatsen
            // DES.P waarde moet verminderd worden met 1 omdat de eerste String positie op 0 begint
            for (int i = 0; i < reeks.Length; i++)
            {
                uitvoer.Append(blok[reeks[i] - 1]);
            }

            return uitvoer.ToString();
        }

        #endregion

        #region ### Function F ###

        private static String F(String R, String sK, int sBoxNr)
        {
            // Expansie
            String uitbreiding = E(R);

            // XORen van de uitbreiding met de subkey
            String xor = XOR(uitbreiding, sK);

            // Uitkomst van de XOR wordt door de Sbox van 6bit naar 4 bit omgevormd
            String sbox = sBox(xor);

            // Permutatie van de 32bits (4b x 8sboxen)
            String p = P(sbox);

            return p;
        }

        #region --- Subfuncties van F ---

        private static String E(String R)
        {
            // E(xpansie) functie
            String uitvoer = permuteerBlok(R, DES.E);

            return uitvoer;
        }

        private static String XOR(String rij1, String rij2)
        {
            StringBuilder berekend = new StringBuilder(rij1.Length);

            // Per positie nagaan of de bits uit beide rijen overeenkomen
            for (int i = 0; i < rij1.Length; i++)
            {
                if (rij1[i] != rij2[i])
                {
                    berekend.Append("1");
                }
                else
                {
                    berekend.Append("0");
                }
            }

            return berekend.ToString();
        }

        private static String sBox(String invoer)
        {
            StringBuilder uitvoer = new StringBuilder(32);

            // Invoer opdelen in stukken van 6bits en door de Sbox sturen
            for (int i = 0; i < 8; i++)
            {
                String stukje = invoer.Substring(i * 6, 6);
                uitvoer.Append(ordenSBox(stukje, i));
            }

            return uitvoer.ToString();
        }

        private static String P(String invoer)
        {
            String uitvoer = permuteerBlok(invoer, DES.P);

            return uitvoer;
        }

        private static String ordenSBox(String invoer, int nrSBox)
        {
            String uitvoer = "";

            // Eerste en laatste bit samen nemen en omzetten naar een int
            int i = Convert.ToInt32(invoer.Substring(0, 1) + invoer.Substring(5, 1), 2);

            // Tweede tem vijfde bit samen nemen en omzetten naar een int
            int j = Convert.ToInt32(invoer.Substring(1, 4), 2);

            // Getal selecteren afgaande op de rij (i) en kolom (j), eventueel een voorloop 0 toevoegen
            int[,] sBox = DES.SBOXES[nrSBox];
            uitvoer = Convert.ToString(sBox[i, j], 2).PadLeft(4, '0');

            return uitvoer;
        }

        #endregion

        #endregion

        #region ### Berekenen SubKeys ###

        private static String[] berekenSubKeys(String sleutel)
        {
            // Sleutel omzetten van karakter naar binair (eventueel voorloop 0 bijvoegen)
            String binaireSleutel = "";

            // Sleutel wordt omgezet naar binair
            StringBuilder sb = new StringBuilder();
            foreach (byte b in Encoding.UTF8.GetBytes(sleutel))
            {
                sb.Append(Convert.ToString(b, 2));
            }

            // Veelvoud van 64 behouden door voorloop nullen aan te vullen
            binaireSleutel = sb.ToString().PadLeft(64, '0');

            String C0, D0;
            String[] Cn = new String[17];
            String[] Dn = new String[17];
            String[] Kn = new String[16];

            // Permuted Choice 1
            String gepermuteerd = permuteerBlok(binaireSleutel, DES.PC_1);

            // Opsplitsen van de sleutel
            C0 = gepermuteerd.Substring(0, 28);
            D0 = gepermuteerd.Substring(28, 28);
            Cn[0] = C0;
            Dn[0] = D0;

            // 16 subkeys aanmaken
            for (int i = 1; i < 17; i++)
            {
                // Links verschuiven
                Cn[i] = linksSchuiven(Cn[i - 1], DES.SHIFTS[i]);
                Dn[i] = linksSchuiven(Dn[i - 1], DES.SHIFTS[i]);

                // Permuted Choice 2
                Kn[i - 1] = permuteerBlok(Cn[i] + Dn[i], DES.PC_2);
            }

            return Kn;
        }

        private static String linksSchuiven(String teVerschuiven, int aantalPlaatsen)
        {
            // Substring nemen van x tekens vanaf het begin
            String stukje = teVerschuiven.Substring(0, aantalPlaatsen);

            // Substring nemen vanaf x tekens tot het einde en hierachter het te verschuiven deel plaatsen
            StringBuilder uitvoer = new StringBuilder(teVerschuiven.Length);
            uitvoer.Append(teVerschuiven.Substring(aantalPlaatsen) + stukje);

            return uitvoer.ToString();
        }

        #endregion

    }
}
